package algo_lignes_sites.data.pre_trt.pre_trt_path.simplif_topo;

import algo_lignes_sites.data.junction.A_Junction;
import algo_lignes_sites.data.junction.JunctionREF;
import algo_lignes_sites.data.path.A_Path;
import algo_lignes_sites.data.path.PathREF;
import commons.logs.Logger;
import gnu.trove.set.hash.THashSet;
import java.util.ArrayList;
import java.util.HashSet;


// Est utile surtout si le rayon de simplification de node est plus petit que la zone de simplif topo ...
// Cela peut arriver souvent si des nodes sont proche obligeant la zone de simplif node a etre reduite
// Cela peut aussi arriver si on choisit des ZI et sont un rayon de simplif node petit pour certains
//  postes pour lesquels on souhaite faire sortir des détails type piquage
//  -> ce pattern évite de conserver les boucles, qui sont toujours moches si elles devaient sortir du poste
// Par ailleurs si une boucle est noyée dans
public class Pattern_BoucleDansNode {


	static boolean traite() {

		Logger.log_DEBUG1("##### BOUCLE PATTERN BoucleDansNode #####");

		boolean bAtLeatOneRealisation = false;

		boolean bReBoucle = true;
		while (bReBoucle) {
			bReBoucle = false;
			Logger.log_DEBUG1("~~~~ sous-boucle PATTERN BoucleDansNode ~~~~");


			int nbNode = SimplifTopo.vNodeREF.size();
			for (int i=0; i<nbNode; i++) {
				JunctionREF node = SimplifTopo.vNodeREF.get(i);

				if (etudiePattern_pourCeNode(node)) {
					bReBoucle = true;
					bAtLeatOneRealisation = true;
				}
			}
		}

		// On n'a pas besoin du SPI de point multiple pour ce pattern
		// mais des pt multiples ont pu disparaitre durant le traitement
		// donc on met a jour ce spi en fin de boucle
		// (pas la peine de le faire apres chaque realisation unitaire de pattern)
		if (bAtLeatOneRealisation)
			SimplifTopo.reinitDataPtMultiple();

		return bAtLeatOneRealisation;
	}

	private static double getRayonZS(JunctionREF node) {
		return node.getRayonNodeFinal() * SimplifTopo.params.factor_SimplifTopo;
	}

	private static boolean etudiePattern_pourCeNode(JunctionREF node) {
		// Critère : Parmi l'ensemble des path connectés au node et avec au moins une extrémité dans la ZS,
		// si 2 path partent du poste et se rejoignent en un CONNECT_3, on n'en garde qu'un des 2
		//  idem si les 2 extrémité sont les mêmes CONNECT_3
		//  (on garde celui qui contient le moins d'intersections)
		// Ces path ne doivent pas sortir de la ZS, et les CONNECT_3 doivent également être dans la ZS
		// Enfin ces path ne doivent pas être dans la ZS d'un autre node ! sinon, on risquerait de transformer
		//  une double connexion entre poste en une unique cf entre JUINE et BVUE5

		double rayonZS = getRayonZS(node);
		// On ne va jamais au dela de la ZI nulle (en dehors, on souhaite conserver les détails)
		//double rayonZIN = node.getRayonNodeFinal() * node.nodeData.contrainteGeom.influenceTotale * node.nodeData.contrainteGeom.influenceNulle;
		//rayonZS = Math.min(rayonZS, rayonZIN);
		// NON JUSTEMENT : c'est pour pouvoir utiliser des ZI tres petites qu'il faut a tout prix enlever ces detail pas beaux (boucle)
		//  donc on se fiche des tailles des ZI

		ArrayList<A_Path> vPath = getPathAutourNodeEtCompatibleWithPattern(node, rayonZS);

		int nbPath = vPath.size();
		for (int i=0 ; i<nbPath ; i++) {
			A_Path path1 = vPath.get(i);
			
			for (int j=i+1 ; j<nbPath ; j++) {
				A_Path path2 = vPath.get(j);
				if (path2==path1) continue; // je ne serais jamais assez prudent !

				if (isDuoPathCompatibleWithPattern(path1, path2)) {
					realisePattern (path1, path2);
					Logger.log_DEBUG1(" => OK pour " + node.getInfoBulle() + " / " + path1.getInfoBulle() + " / " + path2.getInfoBulle());
					SimplifTopo.nbPatternRealise_BoucleDansNode++;
					// On sort car on a modifié les path passant par le node
					//  on reviendra tester ce node dans une boucle ultérieure
					// Pas efficace si beaucoup de pattern OK pour ce node, mais ce ne sera pas le cas
					//  car simplif node est passé avant et a enlevé quasiment tous les pattern
					// Ce n'est que pour les nodes dont on a voulu, au cas par cas, reduire le rayon de simplif
					//  topo qu'il risque d'y avoir pas mal de pattern OK
					return true;
				}
			}
		}

		return false;
	}

	private static ArrayList<A_Path> getPathAutourNodeEtCompatibleWithPattern(JunctionREF node, double rayonZS) {
		ArrayList<A_Path> vRes = new ArrayList<A_Path>();
		THashSet<A_Path> setPathDejaParcourus = new THashSet<A_Path>();

		for (A_Path path : node.getVPathContenant())
			getPathAutourNodeEtCompatibleWithPattern_rec(path, node, rayonZS, vRes, setPathDejaParcourus);

		return vRes;
	}
	private static void getPathAutourNodeEtCompatibleWithPattern_rec(A_Path path, JunctionREF node, double rayonZS, ArrayList<A_Path> vRes, THashSet<A_Path> setPathDejaParcourus) {
		if (setPathDejaParcourus.contains(path))
			return;
		setPathDejaParcourus.add(path);

		// Est-on allé trop loin ?
		if (path.from().distTo(node) > rayonZS || path.to().distTo(node) > rayonZS)
			return;

		if (isPathCompatibleWithPattern(path, node, rayonZS))
			vRes.add(path);

		// A partir de l'extrémité 1
		A_Junction extr1 = path.from();
		if (extr1.isConnect2() || extr1.isConnect3() || extr1.isIntersect()) {
			for (A_Path path2 : extr1.getVPathContenant_nonTraversant())
				if (path2 != path)
					getPathAutourNodeEtCompatibleWithPattern_rec(path2, node, rayonZS, vRes, setPathDejaParcourus);
		}
		// Puis de l'extrémité 2
		A_Junction extr2 = path.to();
		if (extr2.isConnect2() || extr2.isConnect3() || extr2.isIntersect()) {
			for (A_Path path2 : extr2.getVPathContenant_nonTraversant())
				if (path2 != path)
					getPathAutourNodeEtCompatibleWithPattern_rec(path2, node, rayonZS, vRes, setPathDejaParcourus);
		}
	}


	private static boolean isPathCompatibleWithPattern(A_Path path, JunctionREF node, double rayonZS) {
		if ((path.from()==node || path.from().isConnect3() || path.from().isIntersect()) &&
			(path.to()==node || path.to().isConnect3() || path.to().isIntersect()) &&
			
			// les 2 tests sont compris dans le dernier mais ils permettent de discriminer plus rapidement les cas faciles et fréquents
			path.from().distTo(node) <= rayonZS &&
			path.to().distTo(node) <= rayonZS &&
			
			path.areAllJunctionInCircle(node, rayonZS) &&

			// le path doit etre uniquement dans le node en cours, sinon on pourrait transformer une double connexion entre node par une unique
			// dans un cas ou  Node1 -> connect3 -> 2 path // -> connect_3 -> Node2
			//  et si ces 2 nodes sont a cheval
			pathNotInOtherNode(path, node, rayonZS)
			)
			return true;
		else
			return false;
	}

	private static boolean pathNotInOtherNode(A_Path path, JunctionREF node, double rayonZS) {
		// Recherche des autres nodes dont la ZS peut intersecter celle du node en cours
		// On ne connait la taille max des autres rayonZS des autres nodes ...
		// Dans le doute, on imagine qu'ils sont au pire d'une taille 2 fois plus grande ...
		ArrayList<JunctionREF> vNodeProche = SimplifTopo.spiNode.getPointsAtDist(node, rayonZS + rayonZS*2, false);
		int nbNodeProche = vNodeProche.size();

		for (int i=0; i<nbNodeProche ; i++) {
			JunctionREF autreNode = vNodeProche.get(i);
			if (autreNode==node) continue;
			double rayonZS_autreNode = getRayonZS(autreNode);
			if (autreNode.distTo(node) > (rayonZS+rayonZS_autreNode) ) continue; // autreNode trop loin pour intersecter la ZS du node en cours

			// Le path ne doit jamais être dans la ZS de l'autre node
			if (path.isAtLeastOneJunctionInCircle(autreNode, rayonZS_autreNode))
				return false;
		}
		return true;
	}


	// Les path doivent avoir les mêmes extrémités
	private static boolean isDuoPathCompatibleWithPattern(A_Path path1, A_Path path2) {
		return (path1.from()==path2.from() && path1.to()==path2.to()) ||
				(path1.from()==path2.to() && path1.to()==path2.from());
	}
	

	private static void realisePattern(A_Path path1, A_Path path2) {
		assert(path1!=path2);
		// On ne conserve le path qui a le moins d'intersection et si meme nombre, le plus petit
		int nbIntersect1 = path1.getNbJunctionIntersect();
		int nbIntersect2 = path2.getNbJunctionIntersect();
		double lg1 = path1.getLength();
		double lg2 = path2.getLength();

		A_Path pathToKeep, pathToRemove;

		if (nbIntersect1 < nbIntersect2) {
			pathToKeep = path1;
			pathToRemove = path2;
		} else if (nbIntersect1 < nbIntersect2) {
			pathToKeep = path2;
			pathToRemove = path1;
		} else {
			if (lg1 < lg2) {
				pathToKeep = path1;
				pathToRemove = path2;
			} else {
				pathToKeep = path2;
				pathToRemove = path1;
			}
		}

		// Nettoyage du path qui va ete supprimé
		for (A_Junction j : pathToRemove.getVJunction()) {
			j.removePathContenant(pathToRemove);
			// On doit recalculer le type de j
			if (j.getNbPathContenant()>0)
				j.majTypeJunction();
		}

		// Suppression du path
		SimplifTopo.vPathREF.remove((PathREF)pathToRemove);

		// attention, si le path fait partie des path modified car deja impliqué dans un precedent pattern realise
		// il faut l'enlever, car sinon, on va le donner à manger au simplificateur de path et notamment
		// il sera sous-decoupé, et on va alors créer des junctions appartenant a un path supprime ! (va suivre alors une serie de bug tres genants)
		SimplifTopo.vPathREF_modified.remove((PathREF)pathToRemove);

		// On indique le path_toGrow comme modifié
		SimplifTopo.vPathREF_modified.add((PathREF)pathToKeep);

		// pas de SPI à mettre à jour maintenant
		// On MAJ le spi de point multiple en fin de boucle globale de ce pattern
	}

}
